<?php
/*
 * Copyright (c) 2010, webvariants GbR, http://www.webvariants.de
 *
 * Diese Datei steht unter der MIT-Lizenz. Der Lizenztext befindet sich in der
 * beiliegenden LICENSE Datei und unter:
 *
 * http://www.opensource.org/licenses/mit-license.php
 * http://de.wikipedia.org/wiki/MIT-Lizenz
 */

/**
 * Öffentliche API
 *
 * In dieser Klasse sind alle Methoden gesammelt, die dazu gedacht sind, von
 * externen Komponenten aufgerufen zu werden.
 *
 * @author Christoph
 */
abstract class WV24_RealURL2
{
	protected static $fragments = array();  ///< array  Laufzeit-Cache für Fragmente
	protected static $urls      = array();  ///< array  Laufzeit-Cache für URLs
	
	/**
	 * URL-Fragment ermitteln
	 *
	 * Diese Methode gibt für einen Artikel das zu verwendende Fragment zurück.
	 *
	 * @param  mixed $article  null für den aktuellen Artikel, ansonsten irgendwas, was nach einem Artikel aussieht
	 * @param  int   $clang    die Sprach-ID (nur relevant, wenn für $article kein OOArticle-Objekt übergeben wurde)
	 * @return string          das eingegebene Fragment (aus der Metadaten-Seite im Backend)
	 */
	public static function getURLFragment($article = null, $clang = WV_Redaxo::CLANG_CURRENT)
	{
		if ($article === null) {
			$article = WV_Redaxo::getCurrentArticle();
		}
		elseif (!($article instanceof OOArticle)) {
			$clang   = WV_Redaxo::clang($clang);
			$article = OOArticle::getArticleById(WV_Redaxo::getArticleID($article), $clang);
		}
		
		return $article->getValue($article->isStartPage() ? 'wv24_catpath_fragment' : 'wv24_path_fragment');
	}
	
	/**
	 * Entfernt die Domain aus einer URL
	 *
	 * Sollte die URL auf den aktuellen Server zeigen, wird diese Methode den
	 * Hostnamen aus der URL entfernen. Der aktuelle Server wird aus
	 * <code>$_SERVER['SERVER_NAME']</code> ausgelesen.
	 *
	 * @param  string $url  die URL, aus die Domain wenn möglich entfernt werden soll
	 * @return string       die ggf. geänderte URL
	 */
	public static function removeDomainIfPossible($url)
	{
		$thisDomain = $_SERVER['SERVER_NAME'];
		$urlDomain  = substr($url, 7, strpos($url, '/', 7) - 7);
		
		if ($urlDomain == $thisDomain) {
			$url = substr($url, 7 + strlen($thisDomain));
			
			if (empty($url)) {
				$url = '/';
			}
		}
		
		return $url;
	}
	
	/**
	 * Wrapper um http_build_query()
	 *
	 * Diese Methode wrapped die PHP-eigene http_build_query()-Funktion. Der
	 * Query-String kann auch bereits als String angegeben werden. Wenn dies der
	 * Fall ist, wird der $divider am Anfang abgeschnitten (falls er dort
	 * fälschlicherweise steht).
	 *
	 * @param  mixed  $params   die Parameter als assoziatives Array über ein bereits fertiger String
	 * @param  string $divider  das Trennzeichen zwischen einzelnen Werten
	 * @return string           der fertige Query-String im Format mit einem Fragzeichen am Anfang (?foo=bar)
	 */
	public static function buildQuery($params, $divider = '&amp;')
	{
		$params = is_array($params) ? http_build_query($params, '', $divider) : $params;
		
		if (!empty($params)) {
			if (substr($params, 0, strlen($divider)) == $divider) {
				$params = substr($params, strlen($divider));
			}
			
			$params = '?'.$params;
		}
		
		return $params;
	}
	
	/**
	 * URL für einen Artikel ermitteln
	 *
	 * Diese Methode sollte man aufrufen, wenn man für einen Artikel die URL
	 * ermitteln möchte. Sie führt ein entsprechendes Caching durch und wird
	 * automatisch generateURL() aufrufen, sollte die URL noch nicht im Cache
	 * liegen.
	 *
	 * @param  mixed   $article      null für den aktuellen Artikel, ansonsten irgendwas als Artikel identifizierbares
	 * @param  int     $clang        die Sprach-ID
	 * @param  array   $params       GET-Parameter als assoziatives Array
	 * @param  string  $divider      das Trennzeichen für den Query-String
	 * @param  boolean $forceDomain  wenn true, wird immer eine Domain-Angabe ("http://.../") erzeugt (andernfalls nur, wenn nötig)
	 * @return string                die fertige URL für den Artikel (i.d.R. absolut zur Domain)
	 */
	public static function getURL($article = null, $clang = WV_Redaxo::CLANG_CURRENT, $params = array(), $divider = '&amp;', $forceDomain = false)
	{
		$articleID = $article === null ? WV_Redaxo::getCurrentArticleID() : WV_Redaxo::getArticleID($article);
		$clang     = WV_Redaxo::clang($clang);
		$params    = self::buildQuery($params, $divider);
		$key       = $articleID.'_'.$clang;
		
		if (isset(self::$urls[$key]) && !$forceDomain) {
			return self::removeDomainIfPossible(self::$urls[$key]).$params;
		}
		
		// URL schon im Cache?
		
		$sql = WV_SQLEx::getInstance();
		$url = $sql->saveFetch('url', 'wv24_cache', 'article_id = ? AND clang = ?', array($articleID, $clang));
		
		if (!empty($url) && !$forceDomain) {
			self::$urls[$key] = $url;
			return self::removeDomainIfPossible($url).$params;
		}
		
		// Cache-Miss: URL neu generieren
		
		$article = OOArticle::getArticleById($articleID, $clang);
		$path    = $article->getValue('path');
		$url     = self::generateURL($articleID, $clang, true);
		$hash    = self::hashURL($url);
		
		$sql->queryEx(
			'INSERT INTO #_wv24_cache (`article_id`, `clang`, `path`, `url`, `hash`, `created`) VALUES (?,?,?,?,?,NOW())',
			array($articleID, $clang, $path, $url, $hash), '#_'
		);
		
		if (!empty($url) && !$forceDomain) {
			self::$urls[$key] = $url;
			$url = self::removeDomainIfPossible($url).$params;
		}
		
		return $url;
	}
	
	/**
	 * URL für einen Artikel erzeugen
	 *
	 * Diese Methode erzeugt für einen Artikel eine URL. Sie wird intern von
	 * getURL() aufgerufen. Diese Methode cached die URLs nicht zwischen, sondern
	 * ist einzig für das Generieren zuständig.
	 *
	 * @param  mixed   $article        null für den aktuellen Artikel, ansonsten irgendwas als Artikel identifizierbares
	 * @param  int     $clang          die Sprach-ID
	 * @param  boolean $includeDomain  wenn true, wird eine Domain-Angabe ("http://.../") erzeugt (andernfalls nur, wenn nötig)
	 * @return string                  die fertige URL für den Artikel (i.d.R. absolut zur Domain)
	 */
	public static function generateURL($article = null, $clang = WV_Redaxo::CLANG_CURRENT, $includeDomain = false)
	{
		global $REX;
		
		$articleID = $article === null ? WV_Redaxo::getCurrentArticleID() : WV_Redaxo::getArticleID($article);
		$clang     = WV_Redaxo::clang($clang);
		$article   = OOArticle::getArticleById($articleID, $clang);
		
		// Wenn es sich auch noch um den Start-Artikel handelt, verlinken wir auf den Root der Seite.
		
		if ($article->getId() == $REX['START_ARTICLE_ID'] && $clang == $REX['START_CLANG_ID']) {
			if ($_SERVER['SERVER_NAME'] != self::getDefaultDomain()) {
				$url = self::getDefaultDomain(true).'/';
			}
			else {
				$url = self::getBase(true);
			}
		
			// Bei Links, die auf andere Domains zeigen, fügen wir das "http:" vor das "//" ein.
			
			if (self::isDomainURL($url)) {
				$url = 'http:'.$url;
			}
			
			return $url;
		}
		
		$path = trim($article->getValue('path'), '|');
		$path = empty($path) ? array() : explode('|', $path);
		
		// Alle Artikel sammeln, die im Pfad vorkommen
		
		$fragments = array();
		
		if (!empty($path)) {
			$fragments = self::getFragments($path, $clang);
		}
		
		$fragment = self::generateFragment($article);
		$url      = $fragment.'.html';
		$parent   = $article->getParentId();
		
		while ($parent > 0 && !self::isAbsoluteURL($url)) {
			$parentArticle  = OOArticle::getArticleById($parent, $clang);
			$parentFragment = self::generateFragment($parentArticle, $fragments[$parent]['wv24_catpath_fragment']);
			$parent         = $fragments[$parent]['re_id'];
			
			$url = $parentFragment.'/'.$url;
		}
		
		// Sprachfragment als erstes einfügen
		
		if (count($REX['CLANG']) > 1) {
			$url = _WV24_RealURL2::getLanguageFragment($clang).'/'.$url;
		}
		
		// Szenario: Kat1 -> Kat2 -> Kat3
		// Kat1-Fragment: //domain/foobar
		// Kat2-Fragment: leer
		// Kat3-Fragment: /shortcut
		// Ziel: NICHT /shortcut, sondern //domain/foobar/shortcut!
		// "Fremde" Domains in höher gelegenen Kategorien haben Auswirkungen auf Absolutlinks!
		
		if (self::isAbsoluteURL($url) && !self::isDomainURL($url)) {
			while ($parent > 0) {
				$parentArticle  = OOArticle::getArticleById($parent, $clang);
				$parentFragment = self::generateFragment($parentArticle, $fragments[$parent]['wv24_catpath_fragment']);
				$parent         = $fragments[$parent]['re_id'];
				
				if (substr($parentFragment, 0, 2) == '//') {
					$url = $parentFragment.$url; // kein Slash dazwischen nötig, da $url mit einem / beginnt
				}
			}
		}
		
		// Bei Kategorien erzeugen wir Verzeichnislinks
		
		if ($article->isStartPage()) {
			$url = preg_replace('#\.html$#i', '/', $url);
		}
		
		// Links, die keine spezielle Domain angegeben haben, sollen IMMER auf die
		// Standard-Domain zeigen. Also müssen wir den Host einfügen, falls wir
		// uns nicht dort befinden.
		
		$needsDomain = self::isAbsoluteURL($url) && !self::isDomainURL($url) && $_SERVER['SERVER_NAME'] != self::getDefaultDomain();
		
		if (!self::isDomainURL($url) && ($needsDomain || $includeDomain)) {
			$url = self::getDefaultDomain().(self::isAbsoluteURL($url) ? '' : '/').$url;
		}
		elseif (!self::isDomainURL($url)) {
			// Selbst wenn wir auf dem gleichen Server sind, müssen wir die Basis-URL einfügen,
			// um kompatibel zu REDAXO zu bleiben (3rd Party Code: http://SERVER_NAME/URL_VON_REDAXO)
			$url = self::getBase(true).$url;
		}
		
		// Bei Links, die auf andere Domains zeigen, fügen wir das "http:" vor das "//" ein.
		
		if (self::isDomainURL($url)) {
			$url = 'http:'.$url;
		}
		
		return $url;
	}
	
	/**
	 * Fragmente für eine Menge von Artikeln ermitteln
	 *
	 * Diese Methode ruft aus der Datenbank die Fragmente für die in $pathIDs
	 * angegebenen Artikel ab. Die Fragmente werden dann für den Rest der
	 * Scriptlaufzeit gecached.
	 *
	 * @param  array  $pathIDs  ein Array von Artikel-IDs
	 * @param  int    $clang    die Sprach-ID
	 * @return array            ein array der Form <code>[artikelID: fragment, artikelID: fragment]</code>
	 */
	protected static function getFragments($pathIDs, $clang)
	{
		// Bereits abgerufene Fragmente direkt ermitteln
		
		$fragments = array();
		
		foreach ($pathIDs as $id) {
			if (isset(self::$fragments[$clang][$id])) {
				$fragments[$id] = self::$fragments[$clang][$id];
			}
		}
		
		$missing = array_diff($pathIDs, array_keys($fragments));
		
		// War noch nicht alles im Cache?
		
		if (!empty($missing)) {
			$path    = implode(',', $missing);
			$sql     = WV_SQLEx::getInstance();
			$missing = $sql->getArray('SELECT id, wv24_catpath_fragment, re_id FROM #_article WHERE clang = ? AND id IN ('.$path.')', $clang, '#_');
			
			// Daten für diesen Request cachen (beschleunigt generateCache enorm!)
			
			foreach ($missing as $id => $data) {
				self::$fragments[$clang][$id] = $data;
				$fragments[$id]               = $data;
			}
		}
		
		return $fragments;
	}
	
	/**
	 * Artikel-Fragmente in den Cache laden
	 *
	 * Diese Methode wird aufgerufen, bevor der Cache neu generiert wird. Damit
	 * müssen für die einzelnen Artikel später keine Fragmente durch
	 * getFragments() abgerufen werden.
	 */
	protected static function prefetchFragments()
	{
		$sql      = WV_SQLEx::getInstance();
		$articles = $sql->getArray(
			'SELECT a.pid, a.id, a.clang, a.wv24_catpath_fragment, a.re_id '.
			'FROM #_article a '.
			'LEFT JOIN #_wv24_cache c ON a.id = c.article_id AND a.clang = c.clang '.
			'WHERE a.startpage = 1 AND c.article_id IS NULL',
			array(), '#_'
		);
		
		// Daten für diesen Request cachen (beschleunigt generateCache enorm!)
		
		foreach ($articles as $pid => $data) {
			$clang = (int) $data['clang'];
			$id    = (int) $data['id'];
			
			self::$fragments[$clang][$id] = array('wv24_catpath_fragment' => $data['wv24_catpath_fragment'], 're_id' => (int) $data['re_id']);
		}
	}
	
	/**
	 * Fragment erzeugen
	 *
	 * Diese Methode gibt entweder das vom Benutzer im Backend eingegebene
	 * Fragment oder das aus dem Artikelnamen genrierte Fragment zurück.
	 *
	 * @param  OOArticle $article       der Artikel, für den das Fragment gewünscht ist.
	 * @param  string    $userFragment  wenn bereits bekannt, das eingegebene Fragment, ansonsten null
	 * @return string                   ein URL-freundliches Fragment zum Einfügen in die URL
	 */
	public static function generateFragment(OOArticle $article, $userFragment = null)
	{
		if ($userFragment === null) {
			$userFragment = $article->getValue($article->isStartPage() ? 'wv24_catpath_fragment' : 'wv24_path_fragment');
		}
		
		if (!empty($userFragment)) {
			return $userFragment;
		}
		
		$name = $article->isStartPage() ? $article->getValue('catname') : $article->getName();
		return self::makeURLFriendly($name, 'unbenannter-artikel');
	}
	
	/**
	 * Überführt einen String in ein URL-freundliches Format
	 *
	 * Diese Method entfernt alle Sonderzeichen, übersetzt Umlaute und führt
	 * weitere Veränderungen an $str durch, um ein sauberes URL-Fragment zu
	 * erzeugen.
	 *
	 * @param  string $str          der zu verarbeitende String
	 * @param  string $emptyString  der Alternativ-String, falls $str nach der Verarbeitung leer ist
	 * @return string               die URL-freundliche Variante von $str
	 */
	public static function makeURLFriendly($str, $emptyString = '')
	{
		$string = str_ireplace(array('ä', 'ö', 'ü', 'ß'), array('ae', 'oe', 'ue', 'ss'), $str);
		$string = strtolower($string);
		$string = preg_replace('#[^a-z0-9-]#i', '-', $string);
		$string = trim($string, '-');
		$string = preg_replace('#-+#', '-', $string);
		
		if (empty($string)) {
			$string = $emptyString;
		}
		
		return $string;
	}
	
	/**
	 * Prüft, ob eine URL absolut ist
	 *
	 * Eine URL gilt als absolut, wenn sie mit einem Slash beginnt.
	 *
	 * @param  string $url  die zu überprüfende URL
	 * @return boolean      true, wenn $url mit einem Slash beginnt, sonst false
	 */
	public static function isAbsoluteURL($url)
	{
		return $url[0] == '/';
	}
	
	/**
	 * Prüft, ob eine URL Domain-absolut ist
	 *
	 * Eine URL gilt als Domain-absolut, wenn sie mit zwei Slashes beginnt. In
	 * diesem Fall bezeichnet $url ein Fragment und keine fertige URL. Die
	 * "Fehlbezeichnung" kommt daher, dass $url im Laufe von generateURL() erst
	 * eine "echte" URL wird.
	 *
	 * @param  string $url  die zu überprüfende URL
	 * @return boolean      true, wenn $url mit zwei Slashes beginnt, sonst false
	 */
	public static function isDomainURL($url)
	{
		return substr($url, 0, 2) == '//';
	}
	
	/**
	 * Basis-URL ermitteln
	 *
	 * Diese Methode ermittelt die Basis-URL für das Frontend. Diese wird direkt
	 * aus $_SERVER ermittelt (Angaben in $REX werden ignoriert). In der URL sind
	 * auch evtl. vorhandene Unterverzeichnisse enthalten, zum Beispiel, wenn das
	 * Projekte unter http://www.example.com/projectx/ zu erreichen ist.
	 *
	 * Die erzeugte Basis-URL wird für den Rest der Scriptlaufzeit gecached.
	 *
	 * @param  boolean  $pathOnly  wenn true, wird nur der Pfad ohne Domain zurückgegeben
	 * @return string              die Basis-URL (z.B. "www.example.com/projectx/" oder nur "/projectx/")
	 */
	public static function getBase($pathOnly = false)
	{
		static $cache = array();
		
		$pathOnly = $pathOnly ? 1 : 0;
		
		if (isset($cache[$pathOnly])) {
			return $cache[$pathOnly];
		}
		
		$host   = $pathOnly ? '' : 'http://'.$_SERVER['SERVER_NAME'];
		$subDir = dirname($_SERVER['PHP_SELF']);
		
		if (strlen($subDir) > 1) {
			$host .= $subDir;
		}
		
		if (isset($_SERVER['PATH_INFO'])) {
			$length = -10; // = strlen("/index.php")
			
			if (strlen(dirname($_SERVER['PATH_INFO'])) > 1) {
				$length = $length - strlen(dirname($_SERVER['PATH_INFO']));
			}
			
			$host = substr($host, 0, $length);
		}
		
		$host .= '/';
		
		$cache[$pathOnly] = $host;
		return $host;
	}
	
	/**
	 * Cache leeren
	 *
	 * Diese Methode leert den gesamten Cache (sowohl die erzeugten URLs als auch
	 * alle in WV_Cache liegenden Daten).
	 */
	public static function clearCache()
	{
		$cache = WV_DeveloperUtils::getCache();
		$cache->flush('realurl2', true);
		
		$sql = WV_SQLEx::getInstance();
		$sql->queryEx('TRUNCATE #_wv24_cache', array(), '#_');
	}
	
	/**
	 * Cache neu aufbauen
	 *
	 * Diese Methode baut den URL-Cache neu auf. Auf Wunsch kann der Cache auch
	 * vorher erst geleert werden (hard reset).
	 * 
	 * Die benötigten Fragmente werden einmalig gecached. Es werden immer maximal
	 * 100 URLs auf einmal in die Datenbank geschrieben.
	 *
	 * Die Methode startet ihre eigene Transaktion.
	 *
	 * @param  boolean $clearBeforeStart  wenn true, wird der Cache vorher noch geleert (betrifft nur den URL-Cache, nicht WV_Cache)
	 * @return boolean                    true bei Erfolg, sonst false
	 */
	public static function generateCache($clearBeforeStart = false)
	{
		$sql = WV_SQLEx::getInstance();
		
		// Auf Wunsch leeren wir den Cache vorher
		
		if ($clearBeforeStart) {
			$sql->queryEx('TRUNCATE #_wv24_cache', array(), '#_');
		}
		
		// Alle Artikel finden, für die noch keine Daten im Cache liegen.
		
		$articles = $sql->getArray(
			'SELECT a.pid, a.id, a.clang, a.path '.
			'FROM #_article a '.
			'LEFT JOIN #_wv24_cache c ON a.id = c.article_id AND a.clang = c.clang '.
			'WHERE c.article_id IS NULL',
			array(), '#_'
		);
		
		$generated = count($articles);
		
		if (empty($articles)) {
			return true;
		}
		
		self::prefetchFragments();
		
		$mode = $sql->setErrorMode(WV_SQLEx::THROW_EXCEPTION);
		
		try {
			$sql->beginTransaction();
			
			$maxPerRun = 100;
			$urls      = array();
			$tupel     = '(?, ?, ?, ?, ?, NOW())';
			$query     = 'INSERT INTO #_wv24_cache (article_id, clang, path, url, hash, created) VALUES '.implode(', ', array_fill(0, $maxPerRun, $tupel));
			$statement = $sql->prepare(str_replace('#_', WV_SQL::getPrefix(), $query));
			
			foreach ($articles as $article) {
				$articleID = (int) $article['id'];
				$clang     = (int) $article['clang'];
				$url       = self::generateURL($articleID, $clang, true);
				$hash      = self::hashURL($url);
				$urls[]    = array($articleID, $clang, $article['path'], $url, $hash);
				
				if (count($urls) == $maxPerRun) {
					$data = call_user_func_array('array_merge', $urls); // flatten array (über einen Umweg, da PHP keine Funktion dafür anbietet)
					
					$sql->queryEx($statement, $data);
					
					$urls = array();
					$data = null;
				}
			}
			
			if (!empty($urls)) {
				$count = count($urls);
				$query = 'INSERT INTO #_wv24_cache (article_id, clang, path, url, hash, created) VALUES '.implode(', ', array_fill(0, $count, $tupel));
				$data  = call_user_func_array('array_merge', $urls); // flatten array
				
				$sql->queryEx($query, $data, '#_');
			}
			
			$data = null;
			$urls = null;
			
			$sql->commit();
			$sql->setErrorMode($mode);
			
			return true;
		}
		catch (Exception $e) {
			$sql->cleanEndTransaction(true, $mode, $e, '');
			return false;
		}
	}
	
	/**
	 * Hash einer URL bilden
	 *
	 * Diese Methode hashed eine URL und gibt den CRC32-Wert als String zurück.
	 * Über diesen Hashwert wird schlussendlich die URL in der Datenbank gesucht.
	 *
	 * @param  string $url  die zu verarbeitende URL
	 * @return string       der CRC32-Wert als String
	 */
	public static function hashURL($url)
	{
		return sprintf('%u', crc32($url));
	}
	
	/**
	 * Die hier angegebene Domain muss der entsprechen, die für
	 * den Startartikel gilt.
	 */
	public static function getDefaultDomain($asFragment = true)
	{
		if ($asFragment) {
			$dir      = WV_Redaxo::isBackend() ? dirname(dirname($_SERVER['PHP_SELF'])) : dirname($_SERVER['PHP_SELF']);
			$fragment = rtrim('//'.$_SERVER['SERVER_NAME'].$dir, '/');
			return WV8_Settings::getValue('realurl2', 'default_fragment', $fragment);
		}
		
		return WV8_Settings::getValue('realurl2', 'default_domain', $_SERVER['SERVER_NAME']);
	}
	
	/**
	 * URL dekodieren
	 *
	 * Diese Methode versucht zu einer gegebenen URL den dazugehörigen Artikel
	 * zu ermitteln. Ggf. wird statt einer Core-URL auch eine API-erzeugte
	 * Route gefunden. Es wird dabei, falls die URL nicht auf einen Slash endet,
	 * auch noch die Variante mit einem angefügten Slash gesucht.
	 *
	 * Der Rückgabewert ist ein assoziatives Array mit allen Feldern aus der
	 * jeweiligen Cache-Tabelle der Datenbank.
	 *
	 * Sollte keine passende URL gefunden werden, so wird der Extension-Point
	 * REALURL2_DECODE_URL aufgerufen, der als Subject die URL und als
	 * zusätzlichen Parameter den errechneten Hash im Feld 'hash' enthält.
	 *
	 * @param  string $url  die zu dekodierende URL
	 * @return mixed        false, falls die URL nicht gefunden wurde, sonst ein Array mit den Daten
	 */
	public static function decodeURL($url)
	{
		$sql  = WV_SQLEx::getInstance();
		$hash = self::hashURL($url);
		
		$data = $sql->saveFetch('*', 'wv24_cache', '`hash` = ?', $hash);
		
		// Wenn //domain/foo nicht gefunden wurde, testen wir noch //domain/foo/
		
		if ($data === false && substr($url, -1) != '/') {
			$hash = self::hashURL($url.'/');
			$data = $sql->saveFetch('*', 'wv24_cache', '`hash` = ?', $hash);
		}
		
		// Routen checken
		
		if ($data === false) {
			$data = self::checkForRoute($url);
		}
		
		// Nicht gefunden: EP anbieten
		
		if ($data === false) {
			$hash = self::hashURL($url);
			$data = rex_register_extension_point('REALURL2_DECODE_URL', $url, array('hash' => $hash));
			
			if ($data === $url) {
				$data = false;
			}
		}
		
		return $data;
	}
	
	/**
	 * Auf Route prüfen
	 *
	 * Diese Methode wird von decodeURL() aufgerufen, wenn keine Core-URL
	 * gefunden werden konnte.
	 *
	 * @param  string $url  die zu dekodierende URL
	 * @return mixed        false, falls die URL nicht gefunden wurde, sonst ein Array mit den Daten
	 */
	protected static function checkForRoute($url)
	{
		$sql  = WV_SQLEx::getInstance();
		$hash = self::hashURL($url);
		$data = $sql->saveFetch('*', 'wv24_route_cache', '`hash` = ?', $hash);
		
		// Wenn //domain/foo nicht gefunden wurde, testen wir noch //domain/foo/
		
		if ($data === false && substr($url, -1) != '/') {
			$hash = self::hashURL($url.'/');
			$data = $sql->saveFetch('*', 'wv24_route_cache', '`hash` = ?', $hash);
		}
		
		return $data;
	}
}
